package com.bigknow.appstore.proxy.handler.traffic;

import com.bigknow.appstore.proxy.cache.ICacheManager;
import com.bigknow.appstore.proxy.db.IDBHelper;
import com.bigknow.appstore.proxy.entity.SecurityMeta;
import com.bigknow.appstore.proxy.entity.TCLog;
import com.bigknow.appstore.proxy.enumerate.SecurityType;
import com.bigknow.appstore.proxy.exceptions.ProxyError;
import com.bigknow.appstore.proxy.exceptions.processor.impl.ErrorHelper;
import com.bigknow.appstore.proxy.handler.base.BaseOutboundHandler;
import com.bigknow.appstore.proxy.log.ILogWriter;
import com.bigknow.appstore.proxy.server.IProxyServer;
import com.bigknow.appstore.proxy.vo.RequestVo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.FullHttpResponse;

import java.time.LocalDate;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Created by chaos on 2016/9/29.
 */
public abstract class BaseTCHandler extends BaseOutboundHandler {

    private IDBHelper dbHelper;

    /**
     * key 为rsourceId
     */
    private Map<String, TCLog> map = new ConcurrentHashMap<>();

    private Executor executor = Executors.newSingleThreadExecutor();

    public BaseTCHandler(IProxyServer proxyServer, ICacheManager cacheManager, ILogWriter writer, IDBHelper dbHelper) {
        super(proxyServer, cacheManager, writer);
        this.dbHelper = dbHelper;
    }

    @Override
    public void write(ChannelHandlerContext ctx, FullHttpMessage msg, ChannelPromise promise) {
        RequestVo requestVo = getRequestVo(ctx);
        if (requestVo == null || requestVo.getResourceMeta() == null) {
            ctx.write(msg, promise);
        } else {
            if (msg instanceof FullHttpResponse && requestVo.getResourceMeta()
                    .getSecurityMetaMap().containsKey(getSecurityType())) {
                write0(ctx, (FullHttpResponse) msg
                        , promise, findLog(requestVo.getResourceMeta().getId())
                        , requestVo.getResourceMeta().getSecurityMetaMap().get(getSecurityType()));
            } else {
                ctx.write(msg, promise);
            }
        }
    }

    /**
     * 查找资源的统计数据日志数据，如果在内存中没有找到就
     * 从数据库中找，还是没有就new一个
     *
     * @param resourceId
     * @return
     */
    public TCLog findLog(String resourceId) {
        if (map.containsKey(resourceId)) {
            return map.get(resourceId);
        } else {
            TCLog log = dbHelper.findTCLog(resourceId, getSecurityType());
            if (log == null) {
                log = new TCLog();
                log.setId(UUID.randomUUID().toString());
                log.setCurrentDate(LocalDate.now().toEpochDay());
                log.setResId(resourceId);
                log.setSecurityType(getSecurityType());
            }
            map.put(resourceId, log);
            return log;
        }
    }

    /**
     * 更新日志，异步执行
     *
     * @param tcLog
     */
    public void updateLog(TCLog tcLog) {
        this.executor.execute(new Task(tcLog));
    }

    class Task implements Runnable {

        private TCLog log;

        public Task(TCLog log) {
            this.log = log;
        }

        @Override
        public void run() {
            dbHelper.saveTCLog(log);
        }
    }

    /**
     * 将数据添加到log中，并根据日期进行刷新
     *
     * @param log
     * @param value
     */
    public void addAndUpdate(TCLog log, long value) {
        LocalDate currentDate = LocalDate.now();
        LocalDate logDate = LocalDate.ofEpochDay(log.getCurrentDate());
        //不是当天就重新设置数值，是就累加
        if (currentDate.getDayOfYear() != logDate.getDayOfYear()) {
            log.getCurrentValueOfDay().set(value);
        } else {
            log.getCurrentValueOfDay().addAndGet(value);
        }
        //不是当月就重新设置数值，是就累加
        if (currentDate.getMonth().getValue() != logDate.getMonth().getValue()) {
            log.getCurrentValueOfMonth().set(value);
        } else {
            log.getCurrentValueOfMonth().addAndGet(value);
        }

        //不是当年就重新设置数值，是就累加
        if (currentDate.getYear() != logDate.getYear()) {
            log.getCurrentValueOfYear().set(value);
        } else {
            log.getCurrentValueOfYear().addAndGet(value);
        }
        log.getTotal().addAndGet(value);
        log.setCurrentDate(currentDate.toEpochDay());
        updateLog(log);
    }

    public void judge(ChannelHandlerContext ctx, TCLog log, SecurityMeta securityMeta
            , String attrKey, String errorKey) {
        String limit = securityMeta.getPolicyAttrs().get(attrKey);
        try {
            if (limit != null) {
                long limitValue = Long.parseLong(limit);
                if (getCurrentValue(log, attrKey) > limitValue) {
                    ErrorHelper.throwTCError(ctx.channel(), getRequestVo(ctx), getSecurityType(), errorKey, limitValue
                            , log.getCurrentValueOfDay().get());
                }
            }
        } catch (ProxyError proxyError) {
            throw proxyError;
        } catch (Exception e) {
            return;
        }
    }


    /**
     * 返回安全类型
     *
     * @return
     */
    public abstract SecurityType getSecurityType();

    public abstract void write0(ChannelHandlerContext ctx, FullHttpResponse msg
            , ChannelPromise promise, TCLog log, SecurityMeta securityMeta);

    /**
     * 根据attr参数来获取log的值
     *
     * @param log
     * @param attrKey
     * @return
     */
    public abstract long getCurrentValue(TCLog log, String attrKey);

}
